/**
 * 一个file,在库安全页面只有一个issue，此时，可以先获取行号上下几行，然后点击展开更多
 * taskLink: https://teambition.alibaba-inc.com/task/5e9c1053bfa62c8c185ebfef
 */

import React from 'react';
import PropTypes from 'prop-types';
import { YunxiaoIcon, ButtonIcon } from '@alife/devops-ui';
import hljs from 'utils/hl.js';
import exceed from 'utils/apimap';
import 'components/diff/atom-one-light.scss';
import ListLoading from 'atComponents/ListLoading';
import Issue from './issue';
import { maxBlobSize } from 'utils/constants.js';
import { Message, Balloon } from '@teamix/ui';
import ListEmpty from 'atComponents/ListEmpty';
import './index.scss';
import './file.scss';

// 合并区间
import intl from '../../locale';

function mergeRanges(ranges) {
  if (!(ranges && ranges.length)) {
    return [];
  }

  // Stack of final ranges
  const stack = [];

  // Sort according to start value
  ranges.sort((a, b) => {
    return a[0] - b[0];
  });

  // Add first range to stack
  stack.push(ranges[0]);

  ranges.slice(1).forEach((range) => {
    const top = stack[stack.length - 1];

    if (top[1] < range[0] - 1) {
      // No overlap, push range onto stack
      stack.push(range);
    } else if (top[1] < range[1]) {
      // Update previous range
      top[1] = range[1];
    }
  });

  return stack;
}

export default class File extends React.Component {
  static propTypes = {
    // 在库安全默认展开
    isInProjectSecurity: PropTypes.bool,
    // 在库安全中需要使用引入问题的commitId
    commitId: PropTypes.string,
  };

  static defaultProps = {
    isInProjectSecurity: false,
    commitId: '',
  };

  state = {
    blob: '',
    total_lines: 0,
    issue_list: [],
    isIssuesVisible: false,
    shownLineRange: [],
    toManyBlobs: false,
    // 文件过大时
    isPayLoadTooLarge: false,
  };

  UNSAFE_componentWillMount() {
    if (this.props.isInProjectSecurity) {
      this.onFileToggle();
    }
  }

  // isDown true == 向下请求blob, false == 向上请求blob
  getBlob(cb, from = '', to = '', isDown) {
    const { file, projectId, commitId } = this.props;

    exceed
      .fetch({
        api: 'projects.blobs.v4',
        params: { projectId: projectId || window.FORCE.pathResource.id },
        data: {
          filepath: file.file_name,
          from,
          to,
          ref: commitId,
        },
      })
      .then((result) => {
        if (result.content.length > maxBlobSize) {
          this.setState({
            toManyBlobs: true,
          });

          if (this.state.isIssuesVisible) {
            Message.error({
              title: intl.get({
                id: 'code-assets.components.buildResultIssues.securityFile.TheFileContentIsToo',
                defaultMessage: '文件内容太大，只展示问题列表',
              }),
              duration: 2000,
            });
          }
        } else {
          const { blob } = this.state;
          this.setState({
            blob: isDown ? blob + result.content : result.content + blob,
            total_lines: result.total_lines,
          });

          this.updateShownLineRange([from, to]);
        }

        cb && cb();
      })
      .catch((err) => {
        // 针对文件过大直接不返回问题 errorCode: PAYLOAD_TOO_LARGE
        try {
          const error = err && JSON.parse(err.responseText);
          if (error.errorCode === 'PAYLOAD_TOO_LARGE') {
            this.setState({
              isPayLoadTooLarge: true,
            });

            return;
          }
        } catch (e) {
          console.error(e);
        }

        this.setState({
          toManyBlobs: true,
        });
      });
  }

  getIssuesDetail(cb) {
    const { file, projectId } = this.props;
    // 在详情页issue_id_list长度为1,不需要每50个请求一次
    const { issue_id_list } = file;

    const buildId = file.build_result_id;

    exceed
      .fetch({
        api: 'projects.builds.issues.batch',
        params: { projectId: projectId || window.FORCE.pathResource.id, buildId },
        data: {
          issue_ids: issue_id_list.join(','),
        },
      })
      .then((result) => {
        result.shift();

        this.setState({
          issue_list: result,
        });

        cb && cb(result[0]);
      })
      .fail(console.log);
  }

  onFileToggle = () => {
    const { blob, issue_list, forceUpdateDialog } = this.props;

    if (!this.state.isIssuesVisible) {
      // 获取到detial详情之后，获取到准确的行号，再根据指定行号获取blob
      if (!issue_list) {
        this.getIssuesDetail((detail) => {
          forceUpdateDialog();
          if (!blob) {
            const from = Math.max(detail.begin_line - 3, 1);
            const to = detail.begin_line + 3;
            this.getBlob(forceUpdateDialog, from, to);
          }
        });
      }
    }

    // eslint-disable-next-line react/no-access-state-in-setstate
    this.setState({ isIssuesVisible: !this.state.isIssuesVisible }, () => {
      if (!this.state.isIssuesVisible) {
        forceUpdateDialog();
      }
    });
  };

  // 更新展开的行区间
  updateShownLineRange = (range) => {
    const shownLineRange = [...this.state.shownLineRange];
    shownLineRange.push(range);
    this.setState({ shownLineRange: mergeRanges(shownLineRange) });
  };

  renderBlobLine = (item, i) => {
    const { file, issueFilter, forceUpdateDialog } = this.props;
    const { shownLineRange, issue_list, total_lines } = this.state;
    if (!shownLineRange.length) {
      return null;
    }
    const thisLineNum = Math.max(shownLineRange[0][0] + i, 1);

    const filteredIssueList = issue_list.filter((issue) => {
      if (!issueFilter || issue.level.toLowerCase() === issueFilter) {
        return true;
      }
      return false;
    });

    let isShownLine;
    let isExpandLine;
    let expandStart;
    let expandEnd;
    // let allExpandStart;
    // let allExpandEnd;
    // 向下请求
    let isDown;

    shownLineRange.forEach((range) => {
      const [start, end] = range;
      if (thisLineNum >= start && thisLineNum <= end) {
        isShownLine = true;
      }
      if (thisLineNum === start && thisLineNum > 1) {
        isExpandLine = true;
        expandStart = Math.max(thisLineNum - 20, 1);
        expandEnd = thisLineNum - 1;
        // allExpandStart = 1;
        isDown = false;
      }
      // 判断最后一个区间之后还有无代码
      if (end < total_lines && thisLineNum === end) {
        isExpandLine = true;
        expandStart = thisLineNum + 1;
        expandEnd = thisLineNum + 20;
        // allExpandEnd = total_lines;
        isDown = true;
      }
    });

    if (isExpandLine) {
      return (
        <tr className="blob-line expand-line">
          <td className="blob-line-num">...</td>
          <td className="blob-line-content">
            <a
              onClick={() => {
                this.getBlob(forceUpdateDialog, expandStart, expandEnd, isDown);
              }}
              // style={{ marginRight: 12 }}
            >
              <span style={{ marginRight: 3 }}>{isDown ? '▼' : '▲'}</span>
              {intl.get({
                id: 'code-assets.components.buildResultIssues.securityFile.ExpandRows',
                defaultMessage: '展开20行',
              })}
            </a>
            {/* <a
               onClick={() => {
                 this.getBlob(
                   forceUpdateDialog,
                   isDown ? expandStart : allExpandStart,
                   isDown ? allExpandEnd : expandEnd,
                   isDown,
                 );
               }}
              >
               展开所有行
              </a> */}
          </td>
        </tr>
      );
    }

    if (!isShownLine) {
      return null;
    }

    // 在此行下方展示的issue，只匹配 begin_line
    const thisLineShownIssues = filteredIssueList.filter(
      (issue) => issue.begin_line === thisLineNum,
    );

    // issue命中的行，begin_line~end_line区间中均可
    // const thisLineIssues = filteredIssueList.filter(
    //   issue => issue.begin_line <= thisLineNum && issue.end_line >= thisLineNum
    // );
    const thisLineIssues = thisLineShownIssues;

    const fileExt = file.file_name.indexOf('.')
      ? file.file_name.split('.')[file.file_name.split('.').length - 1]
      : '';

    const codeLine = (
      <tr
        className={`blob-line ${thisLineIssues
          .map((issue) => `blob-line-issue-${issue.id}`)
          .join(' ')}`}
      >
        <td className="blob-line-num">{thisLineNum}</td>
        {fileExt && hljs.getLanguage(fileExt) ? (
          <td
            className="blob-line-content"
            dangerouslySetInnerHTML={{
              __html: hljs.highlight(fileExt, item, true).value,
            }}
          />
        ) : (
          <td className="blob-line-content">{item}</td>
        )}
      </tr>
    );

    if (thisLineShownIssues.length) {
      const issueLine = (
        <tr>
          <td colSpan="3">
            <div className="blob-inline-container">
              {thisLineShownIssues.map((issue) => (
                <Issue issue={issue} file={file} {...this.props} />
              ))}
            </div>
          </td>
        </tr>
      );

      return [codeLine, issueLine];
    }

    return codeLine;
  };

  render() {
    const { file, showIssuesInFile, issueFilter, isInProjectSecurity, commitId, build } =
      this.props;
    const { isIssuesVisible, issue_list, blob, toManyBlobs, isPayLoadTooLarge } = this.state;
    // 原始文件
    const originalFilePath = `/${window.FORCE.pathResource.fullPath}/raw/${
      isInProjectSecurity ? commitId : build.sha
    }/${file.file_name}`;

    const filteredIssueList = issue_list.filter((issue) => {
      if (!issueFilter || issue.level.toLowerCase() === issueFilter) {
        return true;
      }
      return false;
    });

    // 根据issue_count_info算出file的level
    const { issue_count_info } = file;
    if (issue_count_info.blocker) {
      file.level = 'Blocker';
    } else if (issue_count_info.critical) {
      file.level = 'Critical';
    } else {
      file.level = 'Major';
    }

    return (
      <div className="file">
        <div
          className={`file-header file-header-level-${
            issueFilter || file.level.toLocaleLowerCase()
          }`}
          onClick={isInProjectSecurity ? null : this.onFileToggle}
        >
          <div className="file-header-title one-line-ellipsis" title={file.file_name}>
            {file.file_name}
          </div>
          <div className="file-header-right">
            <div className="file-header-right-item" style={{ fontWeight: 'bold' }}>
              {isInProjectSecurity ? (
                <Balloon.Tooltip
                  align="t"
                  trigger={
                    <ButtonIcon
                      type="secondary"
                      component="a"
                      name="folder-line"
                      color="gray"
                      href={`/${window.FORCE.pathResource.fullPath}/blob/${commitId}/${file.file_name}`}
                      target="_blank"
                    />
                  }
                >
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.securityFile.ViewSourceFiles',
                    defaultMessage: '查看源文件',
                  })}
                </Balloon.Tooltip>
              ) : (
                <span>
                  {issueFilter ? file.issue_count_info[issueFilter] : file.issue_id_list.length}{' '}
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.securityFile.Questions',
                    defaultMessage: '个问题',
                  })}
                </span>
              )}
            </div>
            {isInProjectSecurity ? null : (
              <div className="file-header-right-item">
                <YunxiaoIcon size="xs" type={`arrow-${isIssuesVisible ? 'up' : 'down'}-2-line`} />
              </div>
            )}
          </div>
        </div>
        {isPayLoadTooLarge && isIssuesVisible && (
          <div className="blob-container">
            <ListEmpty
              className="build-result-blob-empty"
              image="none"
              content={intl.get({
                id: 'code-assets.components.buildResultIssues.securityFile.SorryTheCurrentFileIs',
                defaultMessage: '抱歉，当前文件过大无法在线浏览',
              })}
              description={
                <a href={originalFilePath} target="_blank" rel="noreferrer">
                  {intl.get({
                    id: 'code-assets.components.buildResultIssues.securityFile.ViewRawFiles',
                    defaultMessage: '查看原始文件',
                  })}
                </a>
              }
            />
          </div>
        )}

        {!isPayLoadTooLarge && isIssuesVisible ? (
          <div>
            {filteredIssueList ? (
              <div>
                {showIssuesInFile && !toManyBlobs ? (
                  <div className="blob-container">
                    {filteredIssueList.filter((item) => !item.begin_line) ? (
                      <div className="file-issues">
                        {filteredIssueList
                          .filter((item) => !item.begin_line)
                          .map((issue) => (
                            <Issue issue={issue} file={file} {...this.props} />
                          ))}
                      </div>
                    ) : null}
                    <table className="blob">
                      <colgroup>
                        <col className="blob-line-num-col" />
                        <col />
                      </colgroup>
                      {blob || blob === '' ? (
                        blob
                          .split('\n')
                          .map((item, i) => this.renderBlobLine(item, i, blob.split('\n').length))
                      ) : (
                        <ListLoading />
                      )}
                    </table>
                  </div>
                ) : (
                  <div className="file-issues">
                    {filteredIssueList.map((issue) => (
                      <Issue
                        issue={issue}
                        file={file}
                        isInProjectSecurity={isInProjectSecurity}
                        {...this.props}
                      />
                    ))}
                  </div>
                )}
              </div>
            ) : (
              <ListLoading />
            )}
          </div>
        ) : null}
      </div>
    );
  }
}
